//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Research.ScientificWorkflow.TridentModel;
using System.Resources;
using Microsoft.Research.ScientificWorkflow.TridentUtilities;
using System.ComponentModel;
using Microsoft.Research.ScientificWorkflow.Validators;

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    /// <summary>
    /// Interaction logic for ConditionParamControl.xaml
    /// </summary>
    public partial class ConditionParamControl : UserControl
    {
        //private bool inputParameter;

        /// <summary>
        /// Parameter Description Model.
        /// </summary>
        private ParameterDescriptionModel parameterModel;

        private BaseModel activityModel;

        private ComposerControl composerCtrl;

        private ParameterFlowType parameterType;

        public ConditionParamControl(ParameterDescriptionModel parameterModel, ParameterFlowType parameterType, BaseModel model)
        {
            InitializeComponent();
            this.parameterModel = parameterModel;
            this.activityModel = model;
            this.parameterType = parameterType;
            this.Loaded += new RoutedEventHandler(ConditionParamControl_Loaded);
            this.Unloaded += new RoutedEventHandler(ConditionParamControl_Unloaded);
            this.Name = this.activityModel.UniqueId + "_" + parameterModel.PropertyName;
        }


        /// <summary>
        /// Loaded event handler for the condition param control.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event arguments</param>
        private void ConditionParamControl_Loaded(object sender, RoutedEventArgs e)
        {
            this.GetComposerControl();
            this.AddInteractionHandlers();
        }

        /// <summary>
        /// UnLoaded event handler for the condition param control.
        /// </summary>
        /// <param name="sender">source</param>
        /// <param name="e">event arguments</param>
        void ConditionParamControl_Unloaded(object sender, RoutedEventArgs e)
        {
            this.RemoveInteractionHandlers();
        }

        /// <summary>
        /// Add the interaction handlers according to the mode.
        /// </summary>
        private void AddInteractionHandlers()
        {
            if (this.composerCtrl != null && this.composerCtrl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                if (this.parameterType == ParameterFlowType.InputParameter)
                {
                    this.AllowDrop = true;
                    string actClass = string.Empty;
                    if (this.activityModel.IsCompositeActivityModel)
                    {
                        actClass = (this.activityModel as CompositeActivityModel).ActivityClass;
                    }
                    else if (this.activityModel.IsSimpleActivityModel)
                    {
                        actClass = (this.activityModel as SimpleActivityModel).ActivityClass;
                    }

                    // No need to add DoubleClick event handler for IfCondition
                    if (!actClass.Equals(UIDesignerCodeConstants.ACTIVITYCLASSIFCONDITION))
                    {
                        this.PreviewMouseDoubleClick += new MouseButtonEventHandler(OnInputParameterDoubleClicked);
                    }
                }
                else
                {
                    this.AllowDrop = false;
                    this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(this.OnOutputParamDragStart);
                }

                this.Drop += new DragEventHandler(this.OnInputParamDrop);
                this.DragOver += new DragEventHandler(this.OnInputParamDragOver);
            }
        }

        /// <summary>
        /// Remove the hanlders on unloading.
        /// </summary>
        private void RemoveInteractionHandlers()
        {
            if (this.composerCtrl != null && this.composerCtrl.CurrentRenderMode == RenderMode.ComposerMode)
            {
                if (this.parameterType == ParameterFlowType.InputParameter)
                {
                    this.AllowDrop = true;
                    string actClass = string.Empty;
                    if (this.activityModel.IsCompositeActivityModel)
                    {
                        actClass = (this.activityModel as CompositeActivityModel).ActivityClass;
                    }
                    else if (this.activityModel.IsSimpleActivityModel)
                    {
                        actClass = (this.activityModel as SimpleActivityModel).ActivityClass;
                    }

                    if (!actClass.Equals(UIDesignerCodeConstants.ACTIVITYCLASSIFCONDITION))
                    {
                        this.PreviewMouseDoubleClick -= new MouseButtonEventHandler(OnInputParameterDoubleClicked);
                    }
                }
                else
                {
                    this.AllowDrop = false;
                    this.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(this.OnOutputParamDragStart);
                }

                this.Drop -= new DragEventHandler(this.OnInputParamDrop);
                this.DragOver -= new DragEventHandler(this.OnInputParamDragOver);
            }
        }

        /// <summary>
        /// Fired on drop over this circle.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// DragEventArgs.
        /// </param> 
        private void OnInputParamDrop(object sender, DragEventArgs e)
        {
            ParameterDragInfo dragInfo = (ParameterDragInfo)e.Data.GetData(typeof(ParameterDragInfo));
            if (null == dragInfo)
            {
                return;
            }

            if (null != dragInfo.SourceModel &&  null != dragInfo.SourceModel.ParentModel)
            {
                Visibility currentVisibility = FetchModelCurrentVisibility(dragInfo.SourceModel.ParentModel);
                if (Visibility.Visible != currentVisibility)
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(
                        Application.Current.MainWindow,
                        TridentResourceManager.GetString("DestinationActivityHiddenError"));
                    return;
                }
            }

            if (dragInfo.Model.DataType != this.parameterModel.DataType)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(Application.Current.MainWindow, TridentResourceManager.GetString("ParameterElemMappingFailed"));
                return;
            }

            // Check if the parameter on which is dropped already databound.
            if (this.parameterModel.IsDatabound)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(Application.Current.MainWindow, TridentResourceManager.GetString("ParameterElemInputParamPreBound"));
                return;
            }
            else
            {
                string validateAgainst = GetValue(WorkflowActivityElement.ValidatePropertyNameProperty) as string;
                if (!ConditionParamControl.CheckValidDrop(dragInfo.ValidateBindings, validateAgainst))
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(
                        Application.Current.MainWindow,
                        TridentResourceManager.GetString("ParameterElemInvalidDropValidationFailed"));
                    return;
                }

                // Create binding.
                this.BindProperty(dragInfo.ActivityName, dragInfo.Model);

                if (this.composerCtrl != null)
                {
                    composerCtrl.RefreshUI();
                }
            }
        }

        /// <summary>
        /// On drag over the input param. In case of the dragged object not being compatible, drop is disallowed.
        /// </summary>
        /// <param name="sender">
        /// Drop area.
        /// </param>
        /// <param name="e">
        /// Drag EventArgs.
        /// </param>
        private void OnInputParamDragOver(object sender, DragEventArgs e)
        {
            ParameterDragInfo dragInfo = (ParameterDragInfo)e.Data.GetData(typeof(ParameterDragInfo));
            if (null == dragInfo)
            {
                e.Effects = DragDropEffects.None;
            }
            else
            {
                e.Effects = DragDropEffects.All;
                if (dragInfo.SourceModel.Equals(this.activityModel) && dragInfo.Model.Name.Equals(this.parameterModel.Name))
                {
                    e.Effects = DragDropEffects.None;
                }
            }
        }

        /// <summary>
        /// Binds the output of the dragged control to the input of the dropped control.
        /// </summary>
        /// <param name="sourceName">
        /// The source name.
        /// </param>
        /// <param name="source">
        /// The source parameter.
        /// </param>
        /// <param name="targetElem">
        /// Name of the target element.
        /// </param>
        /// <param name="target">
        /// The target element.
        /// </param>
        private void BindProperty(
            string sourceName,
            ParameterDescriptionModel source)
        {
            this.parameterModel.CreateBindings(sourceName, source.PropertyName);
        }

        /// <summary>
        /// Fired on double click of the input parameter.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// MouseButtonEventArgs.
        /// </param>
        private void OnInputParameterDoubleClicked(object sender, MouseButtonEventArgs e)
        {
            Dictionary<string, object> draggedDataDiction = ((FrameworkElement)e.OriginalSource).DataContext as Dictionary<string, object>;
            ParameterDescriptionModel paramModel = draggedDataDiction["ParameterDescriptionModel"] as ParameterDescriptionModel;

            // Check if the parameter is already bound.
            if (this.parameterModel.IsDatabound)
            {
                TridentMessageBox.ShowTridentErrorMessageBox(Application.Current.MainWindow, TridentResourceManager.GetString("ParameterElemInputParamPreBound"));
                return;
            }

            // Retreive the current value.
            object currentValue = paramModel.Value;

            // Open the dialog box for the user to input values.
            InputBoxResult result =
                InputBox.Show("Set an Input Parameter", "Enter a new value for the " + paramModel.PropertyName + /* " of " + activityMetadata.Name + */":", currentValue == null ? "" : currentValue.ToString(), !paramModel.IsMandatory);

            if (result.ButtonResult == MessageBoxResult.OK)
            {
                try
                {
                    object parsedValue = null;
                    Type dataType = Type.GetType(paramModel.DataType);

                    // If the type is one of the buitin(in GAC) datatypes then convert it.
                    if (dataType != null)
                    {
                        parsedValue = TypeDescriptor.GetConverter(dataType).ConvertFromString(result.UserInput);
                    }
                    else
                    {
                        // Else just store the string. Conversion happens during creation of component activity model.
                        parsedValue = result.UserInput;
                    }

                    if (parsedValue != null)
                    {
                        paramModel.Value = parsedValue;
                        this.composerCtrl.IsWFCommitted = false;
                    }
                }
                catch (NotSupportedException ex)
                {
                    TridentErrorHandler.HandleUIException(ex);
                    TridentMessageBox.ShowTridentErrorMessageBox(Application.Current.MainWindow, TridentResourceManager.GetString("ParameterElemInvalidInput"));
                }
                catch (Exception ex)
                {
                    TridentErrorHandler.HandleUIException(ex);
                    TridentMessageBox.ShowTridentErrorMessageBox(Application.Current.MainWindow, TridentResourceManager.GetString("ParameterElemInvalidInput"));
                }
            }
        }

        /// <summary>
        /// Fired on mouse left button down, handles the drag start functionality.
        /// </summary>
        /// <param name="sender">
        /// This.
        /// </param>
        /// <param name="e">
        /// MouseButtonEventArgs.
        /// </param>
        private void OnOutputParamDragStart(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                try
                {
                    List<BindingValidator> validateBindings = GetValue(WorkflowActivityElement.ValidateBindingProperty) as List<BindingValidator>;
                    bool validationRequired = (bool)GetValue(WorkflowActivityElement.ValidationRequiredProperty);
                    Dictionary<string, object> draggedDataDiction = ((FrameworkElement)e.OriginalSource).DataContext as Dictionary<string, object>;
                    ParameterDescriptionModel paramModelDragged = draggedDataDiction["ParameterDescriptionModel"] as ParameterDescriptionModel;
                    ParameterDragInfo paramInf = new ParameterDragInfo(
                        paramModelDragged,
                        this.activityModel,
                        validationRequired,
                        validateBindings);

                    DragDrop.DoDragDrop(this, paramInf, DragDropEffects.Copy);
                }
                catch (Exception exception)
                {
                    TridentErrorHandler.HandleUIException(exception);
                    TridentMessageBox.ShowTridentErrorMessageBox(Application.Current.MainWindow, TridentResourceManager.GetString("ParameterElemMappingFailed"));
                }
            }
        }

        private static bool CheckValidDrop(List<BindingValidator> validateBindings, string validateName)
        {
            bool IsValid = true;
            foreach (BindingValidator bindValidator in validateBindings)
            {
                if (!bindValidator.ValidateBinding(validateName))
                {
                    IsValid = false;
                    break;
                }
            }

            return IsValid;
        }

        /// <summary>
        /// Get the composer control instance.
        /// </summary>
        private void GetComposerControl()
        {
            string composerCtrlName = (string)this.GetValue(ComposerControl.ComposerControlNameProperty);
            this.composerCtrl = Application.Current.MainWindow.FindName(composerCtrlName) as ComposerControl;
        }

         /// <summary>
        /// Gets the visibility of the base model.
        /// </summary>
        /// <param name="model">
        /// BaseModel.
        /// </param>
        /// <returns>
        /// Visibility.
        /// </returns>
        private static Visibility FetchModelCurrentVisibility(BaseModel model)
        {
            Visibility currentVisibility = Visibility.Visible;
            if (model != null)
            {
                currentVisibility = model.CurrentParameterBindingVisibility;
            }

            return currentVisibility;
        }
    }
}
